package Protocolo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import Constants.ProtocolConsts;

public class MessageReceiver implements Runnable {
	
	class ThrowerTask extends TimerTask {
		public void run() {
			try {
				if (socketFather.getInputStream().available() == 0) {
					synchronized(bytesReceivedMap) {
						ByteArrayOutputStream bos = new ByteArrayOutputStream();
						int k = socketFather.getLastNotRead(), len;
						while (bytesReceivedMap.get(k) != null) {
							len = bytesReceivedMap.get(k).length;
							bos.write(bytesReceivedMap.get(k));
							bytesReceivedMap.remove(k);
							k += len;
						}
						socketFather.setInputStream(new ByteArrayInputStream(bos.toByteArray()));
						socketFather.setLastNotRead(k);
					}
				}
			} catch (IOException e) {
				//e.printStackTrace();
			}
		}
	}

	MiniTCP socketFather;
	HashMap<Integer, byte[]> bytesReceivedMap;
	HashMap<Integer, Integer> threeDupAckMap;
	MessageSender messageSender;

	public int lastNumRec;
	public int countFastRecovery;

	public MessageReceiver(MiniTCP socketFather) {
		this.socketFather = socketFather;
		this.bytesReceivedMap = new HashMap<Integer, byte[]>();
		this.threeDupAckMap = new HashMap<Integer, Integer>();
		this.messageSender = socketFather.getMsgSender();
		this.lastNumRec = 0;
		this.countFastRecovery = 0;
	}

	public Packet receivePacket() throws Exception {
		byte[] receiveData = new byte[ProtocolConsts._HEADER_SIZE + ProtocolConsts._MSS];
		DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
		socketFather.receive(receivePacket); //recebendo o pacote
		Packet retPacket = new Packet(receivePacket);
		socketFather.setRwnd(retPacket.getField()[ProtocolConsts._JAN_RCPT]);
		return retPacket;
	}

	public void run() {
		Timer timer = new Timer();
		timer.schedule(new ThrowerTask(), 0, 50);
		Packet receivePacketReturn;
		try {
			while (socketFather.getConnected()) {
				receivePacketReturn = receivePacket();
				if (receivePacketReturn.isAck()) {
					int numRec = receivePacketReturn.getField()[ProtocolConsts._NUM_REC];
					if (numRec > socketFather.getLastUnack()) { //NOVO ACK
						if (socketFather.getCwnd() >= socketFather.ssthresh)
							socketFather.setCwnd(socketFather.getCwnd() + (int)(ProtocolConsts._MSS*(double)ProtocolConsts._MSS/socketFather.getCwnd()));

						if (socketFather.isEstimatingRTT && numRec > socketFather.tempByteRTT) { //Calculating estimated timeOutInterval                                                        
							socketFather.sampleRTT = (int)(System.currentTimeMillis() - socketFather.tempStartRTT);
							socketFather.estimatedRTT = (int)(((1 - 0.125)*socketFather.estimatedRTT + 0.125*socketFather.sampleRTT));
							socketFather.devRTT = (int)(((1 - 0.25)*socketFather.devRTT + 0.25*(Math.abs(socketFather.sampleRTT - socketFather.estimatedRTT))));
							socketFather.timeOutInterval = Math.max(1, Math.min(socketFather.estimatedRTT + 4*socketFather.devRTT, 50)); //min com 50? .-.
							socketFather.isEstimatingRTT = false;
						}
						int len;
						while (socketFather.getLastUnack() < numRec) {
							if (socketFather.getCwnd() < socketFather.ssthresh)
								socketFather.setCwnd(socketFather.getCwnd() + ProtocolConsts._MSS);
							threeDupAckMap.remove(socketFather.getLastUnack());
							synchronized(messageSender.byteMap) {
								len = messageSender.byteMap.get(socketFather.getLastUnack()).length;
								messageSender.byteMap.remove(socketFather.getLastUnack());
								socketFather.setLastUnack(socketFather.getLastUnack() + len);
							}
						}
						if (!messageSender.isTimerRunning && socketFather.getSendNext() != socketFather.getLastUnack()) {
							messageSender.failByte = socketFather.getLastUnack();
							messageSender.timer.schedule(messageSender.new SendTask(), socketFather.timeOutInterval);
							messageSender.isTimerRunning = true;
						}
					} else { //ACK REPETIDO
						if (numRec == lastNumRec) {
							countFastRecovery++;
							if (countFastRecovery == 3) {
								socketFather.ssthresh = Math.max(socketFather.getCwnd()/2, 5*ProtocolConsts._MSS);
								socketFather.setCwnd(socketFather.ssthresh + 3*ProtocolConsts._MSS);
							} else if (countFastRecovery > 3) {
								socketFather.setCwnd(socketFather.getCwnd() + ProtocolConsts._MSS);
							}
						} else {
							lastNumRec = numRec;
							countFastRecovery = 1;
						}

						if (threeDupAckMap.get(numRec) == null)
							threeDupAckMap.put(numRec, 1);
						else {
							threeDupAckMap.put(numRec, threeDupAckMap.get(numRec)+1);
							if (threeDupAckMap.get(numRec) == 3) {
								synchronized(messageSender.byteMap) {
									if (socketFather.getSendNext() > numRec) {
										messageSender.sendPacket(messageSender.byteMap.get(numRec), 0, numRec, receivePacketReturn.getField()[ProtocolConsts._NUM_SEQ]+1);
									}
								}
								threeDupAckMap.remove(numRec);
							}
						}
					}
				} else {
					if (receivePacketReturn.getField()[ProtocolConsts._NUM_SEQ] == socketFather.getLastUnack()) {
						byte[] receiveData = new byte[receivePacketReturn.getBis().available()];
						receivePacketReturn.getBis().read(receiveData);

						synchronized(bytesReceivedMap) {
							bytesReceivedMap.put(socketFather.getLastUnack(), receiveData);
							//System.out.println("!!! " + socketFather.getLastNotRead() + "  " + socketFather.getLastUnack());
							if (socketFather.getLastNotRead() > socketFather.getLastUnack())
								socketFather.setLastUnack(socketFather.getLastNotRead());
							else {
								int k = socketFather.getLastUnack();
								while (bytesReceivedMap.get(k) != null) {
									k += bytesReceivedMap.get(k).length;
								}
								socketFather.setLastUnack(k);
							}
						}
						messageSender.sendPacket(null, ProtocolConsts._BIT_ACK, socketFather.getSendNext(), socketFather.getLastUnack());
					} else {
						int numSeq = receivePacketReturn.getField()[ProtocolConsts._NUM_SEQ];
						if (bytesReceivedMap.get(numSeq) == null) {
							byte[] receiveData = new byte[receivePacketReturn.getBis().available()];
							receivePacketReturn.getBis().read(receiveData);
							bytesReceivedMap.put(numSeq, receiveData);
						}
						messageSender.sendPacket(null, ProtocolConsts._BIT_ACK, numSeq, socketFather.getLastUnack());
					}
				}
			}
			timer.cancel();
		}
		catch (Exception e) {}
	}
}